Code Retirement
The knowledge inherent in the implementation of your product is distributed among your team members, your code, and your code-related documentation. Sometimes major changes lead you to discard chunks of code that embody significant past learnings - how do you avoid feeling like that earlier effort was wasted?
Trust Version Control
Part of the solution is that you need to trust your version control system - or if you don't trust it, unpack why. Perhaps there's trauma related to some past code-related disaster - does your current system even make it possible? Do you have backups in place that minimize the risk? Distrust of your day-to-day environment is a far bigger problem than the code-retirement issue, and you should deal with that first.
Once you do trust your version control, you can see that deleting a bunch of unused code doesn't mean forgetting it entirely; it's just part of the version history, making it effectively eternal in most modern systems. That doesn't mean you'll be able to find it!
Retirement Pages
Instead of "forgetting" the code, have a section of your code documentation carved off for "retirement pages". Keep them consistently structured, perhaps with a template, so that you always record
- What code you deleted
- What was interesting about it (what you learned from it)
- Why you don't need it anymore
- Explicit pointers into the version control system for how to find it again
The text matters - you're essentially doing SEO for your dead code, so you want a generic search for a relevant topic to turn it up, but you also want to support anyone on your team who happens to remember some interesting code and can't find it any more - even if you don't predict why they want to find it, sometimes just remembering that it was part of a particular dead project is enough to remember to look for it in the retirement section.
Why Delete Code in the First Place?
In extreme cases, "not deleting code" can turn your codebase into a hoard of old project relics - which get in the way of solving current problems, because you have lots of irrelevant code to search through - and you have to maintain all of this leftover code in the face of refactoring and API improvements. "Excess" code is also a security load, directly and through dependency growth - if you stop using an API, you can drop that module from your dependency "footprint", reducing your attack surface and reducing how much you even need to care about third party security issues.
Deleting code should also improve test performance - since you should have at least unit tests that get deleted along with it, and you'll save time by not running them. (In practice, nobody really has enough tests so this benefit is probably limited, but it at least takes it off the plate for future test development.)
Test coverage is an attempt to explicitly measure this - not in bulk percentages, but being able to tell in advance that the code being deleted wasn't thorougly tested anyway can be an encouragement to do the right thing. Removing insufficiently-tested code is generally easier than adding tests and improves coverage metrics directly, and pointing this out to your team is a way to get them thinking more broadly about the issue than as just a test-writing chore.
Deleting code means deleting bugs
At some scale you can start treating code statistically, and conclude that all things being equal (they're not) if you can remove some amount of code you're bound to lose some number of bugs with it. Obviously there's a huge amount of handwaving here, you're fundamentally working with a graph which isn't uniform - but at a practical level, if you're trying to have fewer bugs, having less code is one way of doing that. (This means you need solid business-case coverage in your tests before trying this, so you don't accidentally delete or break features - but you should be doing that anyway.)
Why Bother Keeping Retired Code?
Primarily you're countering a widespread tendency to assume "we put a lot of work into that, so that means it's valuable and we can't just toss it." I'm not saying that all existing code is disposable - but that what you learned about the problem domain is likely more valuable than the code itself, and sometimes the thing you're actually trying to accomplish doesn't need this particular bit of code after all.
Despite the popularity of design principles like YAGNI that push back against building speculative code - sometimes you need to build some of that code to demonstrate that a particular corner of the design space can't actually be reached. The retirement process helps you explicitly keep the knowledge while dropping the maintenance burden.
Sometimes you build a bit of intricate machinery to solve a hard problem... and then you (or more likely a peer who's looking at the system from a higher level, or just without the in-the-trenches complexity in their head) notice a way to avoid the entire problem with a simpler change somewhere else in the system. It can feel like wasted effort (even though the effort was necessary to trigger noticing the simpler solution) and inspire reflexive resistance to the other approach - being able to perserve and acknowledge the cleverness and problem-exploring experience helps reduce that resistance and enables you to "let the simplicity in."
It can be hard to convince developers of this - after all, lines of code or size of codebase is sometimes still (mistakenly) considered a measure of scale or success (though usually not at the team level) and that can trickle down, even subconsciously, which can lead to teams coming up with justifications for keeping things even when they think they know better. This approach lets you get the best of both worlds - "retired" code feels like it's still around, you can tell stories about it and dig up clever implementations (and their test cases, from the unit tests you retired alongside the code.) At the same time, it's no longer a part of day to day maintenance and doesn't incur technical debt, and it's not a part of the code that new team members need to care about in order to be comfortable working on the project. (They can still learn interesting things from the retired code - and it's in a nice convenient place with context if they want to do their own digging - but more often it will be driven by team "war stories", or explanations of the form "yeah, that does sound like a good idea, you're not wrong to think that, but here's the write up of what happened when we did try it and why we gave up on it.")
Conclusion
Explicit code retirement is a low-effort bit of process that pays off as a project scales up. It reduces friction around simplifying code, and reduces developer push-back against throwing away "effort", while still keeping the benefits of past exploratory learning about the software and the problem space.